import ConfigParser
import subprocess
import threading
import sys
import os

from vboxapi import VirtualBoxManager
_vboxmgr = VirtualBoxManager()

_ACTIVE_DEBUGGERS = dict()

def get_ini_option(option_name):
    config = ConfigParser.ConfigParser()
    config.read( os.path.join(os.path.dirname(sys.argv[0]), "vbox_kd.ini") )
    return config.get("General", option_name)

def start_debugger(machine_id):
    machine = _vboxmgr.vbox.findMachine(machine_id)

    connect_options = machine.getExtraData("KD/ConnectOptions")
    if not connect_options:
        return

    stop_debugger(machine_id)

    pipename = "\\\\.\\pipe\\kd_{}".format(machine.name)
    pipename = pipename.replace(" ", "_")

    windbg_file = get_ini_option("WinDbgFile")
    windbg_args = [ windbg_file,
                    "-k",
                    connect_options.replace("$(pipename)", pipename),
                  ]
    try:
        command = get_ini_option("WinDbgCommand")

        windbg_args.append("-c")
        windbg_args.append(command)
    except ConfigParser.NoOptionError:
        pass

    global _ACTIVE_DEBUGGERS
    _ACTIVE_DEBUGGERS[machine_id] = subprocess.Popen( args=windbg_args,
                                                      cwd = os.path.dirname(windbg_file)
                                                    )

def stop_debugger(machine_id):
    global _ACTIVE_DEBUGGERS
    if machine_id in _ACTIVE_DEBUGGERS:
        _ACTIVE_DEBUGGERS[machine_id].kill()
        del _ACTIVE_DEBUGGERS[machine_id]

_MACHINE_STATE_HANDLERS = { _vboxmgr.constants.MachineState_FirstOnline:    start_debugger,
                            _vboxmgr.constants.MachineState_PoweredOff:     stop_debugger,
                            _vboxmgr.constants.MachineState_Saved:          stop_debugger,
                            _vboxmgr.constants.MachineState_Aborted:        stop_debugger,
                          }

def on_machine_state_changed(event):
    casted_event = _vboxmgr.queryInterface(event, "IMachineStateChangedEvent")

    global _MACHINE_STATE_HANDLERS
    if casted_event.state in _MACHINE_STATE_HANDLERS:
        _MACHINE_STATE_HANDLERS[casted_event.state](casted_event.machineId)

class WaitableEvent:
    def __init__(self):
        self._cv = threading.Condition()

    def wait(self):
        self._cv.acquire()
        self._cv.wait()
        self._cv.release()

    def set(self):
        self._cv.acquire()
        self._cv.notify()
        self._cv.release()

def thread_routine(event_thread_ready, flag_stop_thread):
    listener = _vboxmgr.vbox.eventSource.createListener()
    _vboxmgr.vbox.eventSource.registerListener( listener,
                                                [_vboxmgr.constants.VBoxEventType_OnMachineStateChanged],
                                                False )

    event_thread_ready.set()

    while flag_stop_thread.is_set() == False:
        event = _vboxmgr.vbox.eventSource.getEvent(listener, 500)
        if event:
            on_machine_state_changed(event)

    _vboxmgr.vbox.eventSource.unregisterListener(listener)

def main():
    event_thread_ready = WaitableEvent()
    flag_stop_thread = threading.Event()

    thread = threading.Thread(target=thread_routine, args=(event_thread_ready, flag_stop_thread, ))
    thread.start()

    event_thread_ready.wait()

    vbox_process = subprocess.Popen( [ get_ini_option("VirtualBoxFile"), ] )
    vbox_process.wait()

    flag_stop_thread.set()
    thread.join()

if __name__ == "__main__":
    main()
